﻿#region Copyright (c) 2008 plexBB Team

/*  Copyright (c) 2008 plexBB Team
 *  
 *  This file is free software: you may copy, redistribute and/or modify it  
 *  under the terms of the GNU General Public License as published by the  
 *  Free Software Foundation, either version 2 of the License, or (at your  
 *  option) any later version.  
 *  
 *  This file is distributed in the hope that it will be useful, but  
 *  WITHOUT ANY WARRANTY; without even the implied warranty of  
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  
 *  General Public License for more details.  
 *  
 *  You should have received a copy of the GNU General Public License  
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

#endregion

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Xsl;

namespace PlexBB.Data
{
    public class PlexBBData : DataContext
    {
        #region Constructors

        public PlexBBData(string fileOrServerOrConnection, string tablePrefix) :
            base(fileOrServerOrConnection, GetMappingSourceByPrefix(tablePrefix))
        {
#if DEBUG
            if (HttpContext.Current != null && HttpContext.Current.Request.IsLocal)
            {
                Log = new DebuggerWriter();
            }
#endif
        }

        #endregion

        public Table<User> Users
        {
            get { return GetTable<User>(); }
        }

        public Table<ForumSession> ForumSessions
        {
            get { return GetTable<ForumSession>(); }
        }

        public Table<Module> Modules
        {
            get { return GetTable<Module>(); }
        }

        public Table<Forum> Forums
        {
            get { return GetTable<Forum>(); }
        }

        public Table<GlobalSetting> GlobalSettings
        {
            get { return GetTable<GlobalSetting>(); }
        }

        private void InsertUser(User user)
        {
            ValidateUserNameChange(user);
            ExecuteDynamicInsert(user);
        }

        private void UpdateUser(User user)
        {
            ValidateUserNameChange(user);
            ExecuteDynamicUpdate(user);
        }

        private void ValidateUserNameChange(User user)
        {
            if (user.UserNameDirty)
            {
                bool sameNameExists = (from us in Users
                                       where us.UserName.ToLower() == user.UserName.ToLower() && us.UserID != user.UserID
                                       select us).Count() > 0;
                if (sameNameExists)
                {
                    throw new Exception("User with the same name already exists.");
                }
            }
        }

        #region Mapping source helper code

        private static readonly Dictionary<string, MappingSource> _mappingSourceCache = new Dictionary<string, MappingSource>();
        private static readonly object _mappingSourceLock = new object();

        private static MappingSource GetMappingSourceByPrefix(string tablePrefix)
        {
            tablePrefix = tablePrefix ?? string.Empty;

            MappingSource source;
            // use double-check lock pattern
            if (!_mappingSourceCache.TryGetValue(tablePrefix, out source))
            {
                // lock adding same item to cache twice (from different threads)
                lock (_mappingSourceLock)
                {
                    if (!_mappingSourceCache.TryGetValue(tablePrefix, out source))
                    {
                        // load mapping template and apply prefix to get final mapping xml
                        using (Stream mappingStream = Util.GetResourceStream("Data/MappingTemplate.xml"),
                                      transformStream = Util.GetResourceStream("Data/MappingPrefixReplace.xslt"))
                        {
                            XmlReader mappingTemplateReader = new XmlTextReader(mappingStream);

                            XslCompiledTransform transform = new XslCompiledTransform();
                            transform.Load(new XmlTextReader(transformStream));

                            XsltArgumentList transformArgs = new XsltArgumentList();
                            transformArgs.AddParam("prefix", string.Empty, tablePrefix);

                            using (MemoryStream stream = new MemoryStream())
                            {
                                transform.Transform(mappingTemplateReader, transformArgs, stream);
                                stream.Position = 0;
                                source = XmlMappingSource.FromStream(stream);
                            }
                        }
                        _mappingSourceCache.Add(tablePrefix, source);
                    }
                }
            }
            return source;
        }

        #endregion

        #region Nested types

#if DEBUG
        /// <summary>
        /// Implements a <see cref="TextWriter"/> for writing information to the debugger log.
        /// </summary>
        /// <seealso cref="Debugger.Log"/>
        private class DebuggerWriter : TextWriter
        {
            private static UnicodeEncoding encoding;
            private readonly string category;
            private readonly int level;
            private bool isOpen;

            /// <summary>
            /// Initializes a new instance of the <see cref="DebuggerWriter"/> class.
            /// </summary>
            public DebuggerWriter()
                : this(0, Debugger.DefaultCategory)
            {
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="DebuggerWriter"/> class with the specified level and category.
            /// </summary>
            /// <param name="level">A description of the importance of the messages.</param>
            /// <param name="category">The category of the messages.</param>
            public DebuggerWriter(int level, string category)
                : this(level, category, CultureInfo.CurrentCulture)
            {
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="DebuggerWriter"/> class with the specified level, category and format provider.
            /// </summary>
            /// <param name="level">A description of the importance of the messages.</param>
            /// <param name="category">The category of the messages.</param>
            /// <param name="formatProvider">An <see cref="IFormatProvider"/> object that controls formatting.</param>
            public DebuggerWriter(int level, string category, IFormatProvider formatProvider)
                : base(formatProvider)
            {
                this.level = level;
                this.category = category;
                isOpen = true;
            }

            public override Encoding Encoding
            {
                get
                {
                    if (encoding == null)
                    {
                        encoding = new UnicodeEncoding(false, false);
                    }
                    return encoding;
                }
            }

            public int Level
            {
                get { return level; }
            }

            public string Category
            {
                get { return category; }
            }

            protected override void Dispose(bool disposing)
            {
                isOpen = false;
                base.Dispose(disposing);
            }

            public override void Write(char value)
            {
                if (!isOpen)
                {
                    throw new ObjectDisposedException(null);
                }
                Debugger.Log(level, category, value.ToString());
            }

            public override void Write(string value)
            {
                if (!isOpen)
                {
                    throw new ObjectDisposedException(null);
                }
                if (value != null)
                {
                    Debugger.Log(level, category, value);
                }
            }

            public override void Write(char[] buffer, int index, int count)
            {
                if (!isOpen)
                {
                    throw new ObjectDisposedException(null);
                }
                if (buffer == null || index < 0 || count < 0 || buffer.Length - index < count)
                {
                    base.Write(buffer, index, count); // delegate throw exception to base class
                }
                Debugger.Log(level, category, new string(buffer, index, count));
            }
        }
#endif

        #endregion
    }
}